(in-package #:cl-fast-ecs/tests)

(define-test storage
  :parent cl-fast-ecs)

(define-test created-bit-initially-set
  :parent storage
  (make-storage)
  (is = 1 (sbit (ecs::storage-component-created-bits *storage*)
                ecs::+coordinate-component-index+)))

(define-test "component created and removed bits unset after RUN-SYSTEMS"
  :parent storage
  (make-storage)
  (run-systems)
  (is = 0 (sbit (ecs::storage-component-created-bits *storage*)
                ecs::+coordinate-component-index+))
  (is = 0 (sbit (ecs::storage-component-removed-bits *storage*)
                ecs::+coordinate-component-index+)))

(define-test "component created bit set after adding a component"
  :parent storage
  (make-storage)
  (let ((_ (run-systems))
        (entity (make-entity)))
    (declare (ignore _))
    (make-coordinate entity)
    (is = 1 (sbit (ecs::storage-component-created-bits *storage*)
                  ecs::+coordinate-component-index+))))

(define-test "component removed bit set after removing a component"
  :parent storage
  (make-storage)
  (let ((entity (make-entity)))
    (make-coordinate entity)
    (run-systems)
    (delete-coordinate entity)
    (is = 1 (sbit (ecs::storage-component-removed-bits *storage*)
                  ecs::+coordinate-component-index+))))

(define-test storage-initial-allocated
  :parent storage
  (fail (make-storage :initial-allocated 0))
  (fail (make-storage :initial-allocated 1))
  (let* ((initial-allocated 42)
         (storage (make-storage :initial-allocated initial-allocated)))
    (is = initial-allocated ecs::*storage-entities-allocated*)
    (is = initial-allocated (length (ecs::storage-deleted-entities storage)))
    (loop :for component :across (ecs::storage-component-storages storage)
          :do (is = initial-allocated
                  (length (ecs::component-soa-exists component)))
              (is = initial-allocated
                  (ecs::component-soa-allocated component)))))

(define-test storage-growth
  :parent storage
  (symbol-macrolet
      ((1st-component-storage
         (svref (ecs::storage-component-storages *storage*) 0)))
    (let* ((initial-allocated 2))
      (make-storage :initial-allocated initial-allocated)
      (make-entity)
      (make-entity)
      (is = initial-allocated ecs::*storage-entities-allocated*)
      (is = initial-allocated
          (length (ecs::component-soa-exists 1st-component-storage)))

      (make-entity)
      (is > initial-allocated ecs::*storage-entities-allocated*)
      (is > initial-allocated
          (length (ecs::component-soa-exists 1st-component-storage))))))

(define-test storage-deleted-growth
  :parent storage
  (let* ((initial-allocated 2))
    (make-storage :initial-allocated initial-allocated)
    (make-entity)
    (make-entity)
    (make-entity)
    (make-entity)
    (is = initial-allocated (length (ecs::storage-deleted-entities *storage*)))

    (delete-entity 0)
    (delete-entity 1)
    (is = initial-allocated (length (ecs::storage-deleted-entities *storage*)))

    (delete-entity 1)
    (is > initial-allocated
        (length (ecs::storage-deleted-entities *storage*)))))

(define-test "component storage min and max entity"
  :parent storage
  (make-storage)
  (let* ((components (ecs::storage-component-storages *storage*))
         (coordinate-component (aref components ecs::+coordinate-component-index+)))
    (is = array-dimension-limit
        (ecs::component-soa-min-entity coordinate-component))
    (is = -1 (ecs::component-soa-max-entity coordinate-component))

    (make-object '())            ;; 0
    (make-object '((:coordinate))) ;; 1
    (make-object '((:coordinate))) ;; 2
    (make-object '())            ;; 3

    (is = 1 (ecs::component-soa-min-entity coordinate-component))
    (is = 2 (ecs::component-soa-max-entity coordinate-component))))

(define-test storage-initialized-hook-called
  :parent components
  (let* ((hook-run-p nil)
         (hook (lambda (s) (declare (ignore s)) (setf hook-run-p t))))
    (false hook-run-p "hook wasn't run initially")
    (hook-up *storage-initialized-hook* hook)
    (make-storage)
    (true hook-run-p "hook run after storage initialization")))

(define-test storage-pretty-printer
  :parent storage
  (true (search
         (format nil "storage of ~a component~:p [0 entities]"
                 ecs::*component-registry-length*)
         (format nil "~a" (make-storage))
         :test #'char-equal))
  (make-entity)
  (true (search
         (format nil "storage of ~a component~:p [1 entity]"
                 ecs::*component-registry-length*)
         (format nil "~a" *storage*)
         :test #'char-equal)))
